package cn.dslcode.spring.session.redis.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by dongsilin on 2018/1/20.
 */
@Slf4j
public class SessionRedisJsonSerializer implements RedisSerializer<Object> {

    private ObjectMapper objectMapper = new ObjectMapper();
    static final byte[] EMPTY_ARRAY = new byte[0];

    private static Map<String, ClassResolve> classNameMap = new ConcurrentHashMap<>();
    private static Map<Byte, ClassResolve> positionMap = new ConcurrentHashMap<>();
    private static AtomicInteger atomicInteger = new AtomicInteger(Byte.MIN_VALUE);

    @Override
    public byte[] serialize(Object object) throws SerializationException {
        if (null == object) return EMPTY_ARRAY;
        String className = object.getClass().getName();
        ClassResolve classResolve = null;
        try {
            if (!classNameMap.containsKey(className)) {
                classResolve = new ClassResolve(Class.forName(className));
                classNameMap.put(className, classResolve);
                positionMap.put(classResolve.position, classResolve);
            } else {
                classResolve = classNameMap.get(className);
            }
            byte[] bytes = this.objectMapper.writeValueAsBytes(object);
            byte[] newBytes = new byte[bytes.length + 1];
            System.arraycopy(bytes, 0, newBytes, 0, bytes.length);
            newBytes[bytes.length] = classResolve.position;
            return newBytes;
        }catch (Exception e){
            log.info("serialize classResolve={}", classResolve);
            log.error("", e);
            return EMPTY_ARRAY;
        }
    }

    @Override
    public Object deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length == 0) return null;
        ClassResolve classResolve = positionMap.get(Byte.valueOf(bytes[bytes.length - 1]));
        byte[] newBytes = new byte[bytes.length - 1];
        System.arraycopy(bytes, 0, newBytes, 0, bytes.length - 1);
        try {
            return this.objectMapper.readValue(newBytes, 0, newBytes.length, classResolve.clazz);
        }catch (Exception e){
            log.info("deserialize classResolve={}", classResolve);
            log.error("", e);
            return null;
        }
    }

    @ToString
    private static class ClassResolve<T>{
        public byte position;
        public Class<T> clazz;
        public ClassResolve(Class<T> clazz) throws Exception {
            int pos = atomicInteger.getAndIncrement();
            if (pos > Byte.MAX_VALUE) throw new Exception("Excess Byte.MAX_VALUE");
            this.position = (byte) pos;
            this.clazz = clazz;
        }
    }

}
